﻿#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <stdbool.h>
#include <math.h>
#include "sort.h"



#define BITMAPFILEHEADERLENGTH 14 //bmp文件的头文件的14字节
#define BM 19778  //BM的ASCII 码
#define THRESHOLD 150  //黑白图像阈值
#define CIRCLECOUNTTHRESHOLD 150

const char* inPath = "./二维码图片/二维码图片/llleft0.bmp";
const char* outPath = "out_left.bmp";

void bmpFileTest(FILE* fpbmp);  //测试当前文件是否是bmp文件
void bmpHeaderPartLength(FILE* fpbmp);  //得到头部的偏移量
void BmpWidthHeight(FILE* fpbmp);  //获得图像的长和宽
void bmpDataPart(FILE* fpbmp);  //获得文件的数据
void bmpoutput(FILE* fpout);  //输出数据到相关的txt文档
void getAngleAndOffset();  //获得角度和图像偏移
void dilateOperation(unsigned char image[][2000], long  width, long  height, int box);
void erodeOperation(unsigned char image[][2000], long  width, long  height, int box);
int getImageIndex();   //得到图像的编号
int find_pre(int i, int j);
bool judge_edge(int x, int y); //判断是否是在边缘
bool judge_circle(int idx); //检查idx点集是否是圆

void color(int i, int j); //test,将点染成红色

unsigned int OffSet = 0;  // 图像的头文件偏移量
long width;  // 图像的宽度
long height;  // 图像的长度
double angle, offset;  //二维码的角度距离，以及编号
long serialNumber;
//图像的彩色值
unsigned char r[2000][2000], output_r[2000][2000]; 
unsigned char g[2000][2000], output_g[2000][2000];
unsigned char b[2000][2000], output_b[2000][2000];
unsigned char gray[2000][2000];  //图像的灰度值
unsigned char tmpimage[2000][2000]; //临时的中间图像，用于各种操作用，避免每次分配
int finded[2000][2000];  //记录地图中是否已经找到了点
int p_count[2000],p_idex=0;  //图像点集以及编号
int idx_point_x[2000][2000],idx_point_y[2000][2000]; //点集编号对应的点(x,y)
unsigned int linedata[3], clinedata[3], dlinedata[3]; //得到普通直线、中心直线、数据直线的数据
int circle_count = 0; 
int circle_center[4][2];  //圆点点中心点

int pjx(int j) {  //转换图像到对应的坐标系中
	return j - width / 2;
}
int piy(int i) {
	return height/2 - i;
}
int main(int argc, char* argv[])
{
	 
	unsigned char* fp_temp; //打开bmp文件
	FILE* fpbmp;
	FILE* fpout;
	fpbmp = fopen(inPath, "rb");
	if (fpbmp == NULL)
	{
		printf("Open bmp failed!!!\n");
		return 1;
	}
	fpout = fopen(outPath, "wb+");
	if (fpout == NULL)
	{
		printf("Open out.bmp failed!!!\n");
		return 1;
	}
	bmpFileTest(fpbmp); 
	bmpHeaderPartLength(fpbmp); 
	BmpWidthHeight(fpbmp);  
	fseek(fpbmp, 0L, SEEK_SET);
	fseek(fpout, 0L, SEEK_SET);
	fp_temp = malloc(OffSet);  
	fread(fp_temp, 1, OffSet, fpbmp);//读取头部内容
	fwrite(fp_temp, 1, OffSet, fpout); //原封不动的写回我们要保存的文件
	bmpDataPart(fpbmp);  //获得文件数据

	/*图片进行处理部分！！！！*/
	dilateOperation(gray, width, height, 3);
	erodeOperation(gray, width, height, 5);
	getAngleAndOffset();  //获得角度和图像偏移

	bmpoutput(fpout);
	fclose(fpbmp);
	fclose(fpout);
	free(fp_temp);
	return 0;
}

//void test_dtect_area();

void bmpoutput(FILE* fpout)
{
	int i, j = 0;
	int stride;
	unsigned char* pixout = NULL;
	stride = (24 * width) / 8;//每行所占的字节数
	stride = (stride + 3) / 4 * 4;//字节数位不足4位补齐为4位
	pixout = malloc(stride);
	if (pixout == NULL) {
		printf("malloc error\n");
		return;
	}
	
	fseek(fpout, OffSet, SEEK_SET);
	for (j = 0; j < height; j++)
	{
		for (i = 0; i < width; i++)
		{
			pixout[i * 3 + 2] = output_r[height - 1 - j][i];
			pixout[i * 3 + 1] = output_g[height - 1 - j][i];
			pixout[i * 3] = output_b[height - 1 - j][i];
			/*pixout[i * 3 + 2] = gray[height - 1 - j][i];
			pixout[i * 3 + 1] = gray[height - 1 - j][i];
			pixout[i * 3] = gray[height - 1 - j][i];*/
		}
		fwrite(pixout, 1, stride, fpout);
	}
}


void bmpDataPart(FILE* fpbmp)
{
	int i, j = 0;
	int stride;
	unsigned char* pix = NULL;
	
	fseek(fpbmp, OffSet, SEEK_SET);
	stride = (24 * width) / 8;//每行所占的字节数
	stride = (stride + 3) / 4 * 4;//字节数位不足4位补齐为4位
	pix = malloc(stride);

	for (j = 0; j < height; j++)
	{
		fread(pix, 1, stride, fpbmp);  //读取图片数据
		for (i = 0; i < width; i++)
		{
			r[height - 1 - j][i] = pix[i * 3 + 2];
			g[height - 1 - j][i] = pix[i * 3 + 1];
			b[height - 1 - j][height - 1 - j] = pix[i * 3];
			if (r[height - 1 - j][i] < THRESHOLD && g[height - 1 - j][i] < THRESHOLD && b[height - 1 - j][i] < THRESHOLD) gray[height - 1 - j][i] = 0;
			else gray[height - 1 - j][i] = 255;
			
			output_r[height - 1 - j][i] = gray[height - 1 - j][i];
			output_g[height - 1 - j][i] = gray[height - 1 - j][i];
			output_b[height - 1 - j][i] = gray[height - 1 - j][i];
		}

	}
	////写入到txt文档中
	//FILE* fpr;
	//FILE* fpg;
	//FILE* fpb;
	//if ((fpr = fopen("bmpr.txt", "w+")) == NULL)
	//{
	//	printf("Failed to construct file bmpr.txt.!!!");
	//	exit(1);
	//}
	//if ((fpg = fopen("bmpg.txt", "w+")) == NULL)
	//{
	//	printf("Failed to construct file bmpg.txt.!!!");
	//	exit(1);
	//}
	//if ((fpb = fopen("bmpb.txt", "w+")) == NULL)
	//{
	//	printf("Failed to construct file bmpb.txt.!!!");
	//	exit(1);
	//}
	//for (i = 0; i < 10; i++)
	//{
	//	for (j = 0; j < width - 1; j++)
	//	{
	//		printf("%4d", gray[i][j]);
	//		fprintf(fpb, "%4d", b[i][j]);
	//		fprintf(fpg, "%4d", g[i][j]);
	//		fprintf(fpr, "%4d", gray[i][j]);
	//	}
	//	printf("%4d\n", gray[i][j]);
	//	fprintf(fpb, "%4d\n", b[i][j]);
	//	fprintf(fpg, "%4d\n", g[i][j]);
	//	fprintf(fpr, "%4d\n", gray[i][j]);
	//}
	//
	//fclose(fpr);
	//fclose(fpg);
	//fclose(fpb);

}


void bmpFileTest(FILE* fpbmp)  //固定的算法，看是否是bmp文件
{
	unsigned short bfType = 0;
	fseek(fpbmp, 0L, SEEK_SET);//seek_set 起始位置
	fread(&bfType, sizeof(char), 2, fpbmp);
	if(bfType!=BM)
	{ 
		printf("This file is not bmp file.!!!\n");
		exit(1);
	}
}

/* 获得文件的头部偏移 */
void bmpHeaderPartLength(FILE* fpbmp)
{
	fseek(fpbmp, 10L, SEEK_SET); //从10字节处开始读
	fread(&OffSet, sizeof(char), 4, fpbmp);
	printf("The Header Part is of length %d.\n", OffSet);
}


/* 获得文件大小 */
void BmpWidthHeight(FILE* fpbmp)
{
	fseek(fpbmp, 18L, SEEK_SET);
	fread(&width, sizeof(char), 4, fpbmp);
	fseek(fpbmp, 22L, SEEK_SET);
	fread(&height, sizeof(char), 4, fpbmp);
	printf("The Width of the bmp file is %ld.\n", width);
	printf("The Height of the bmp file is %ld.\n", height);
}

/* 图像的膨胀操作,box为奇数 */
void dilateOperation(unsigned char image[][2000],  long  width,  long  height,int box) {
	int i, j, t = box / 2;
	for (i = t; i + t< height; i++) {
		for (j = t; j + t < width; j++) {
				//找到当前box中最大的像素值
			int a, b;
			unsigned char maxv = 0;
			for (a = i - t; a <= i + t; a++) {
				for (b = j - t; b <= j + t; b++) {
					if (image[a][b] > maxv) maxv = image[a][b];
				}
			}
			tmpimage[i][j] = maxv;
		}
	}
	//返回给image
	for (i = t; i + t < height; i++) {  
		for (j = t; j + t < width; j++) {
			image[i][j] = tmpimage[i][j];
		}
	}

}

/* 图像的腐蚀操作,box为奇数 */
void erodeOperation(unsigned char image[][2000], long  width, long  height, int box) {
	int i, j, t = box / 2;
	for (i = t; i + t < height; i++) {
		for (j = t; j + t < width; j++) {
			//找到当前box中最大的像素值
			int a, b;
			unsigned char minv = 255;
			for (a = i - t; a <= i + t; a++) {
				for (b = j - t; b <= j + t; b++) {
					if (image[a][b] < minv) minv = image[a][b];
				}
			}
			tmpimage[i][j] = minv;
		}
	}
	//返回给image
	for (i = t; i + t < height; i++) {
		for (j = t; j + t < width; j++) {
			image[i][j] = tmpimage[i][j];
		}
	}
}

int find_pre(int x, int y) {
	int i, j;
	for (i = x-5 ; i <= x+5 ; i++) {
		for (j = y-5 ; j <=y+5; j++) {
			if (finded[i][j]>0) {
				return finded[i][j];
			}
		} 
	}
	return 0;
}

bool judge_edge(int x, int y) {
	int i, j;
	for (i = x - 1; i <= x + 1; i++) {
		for (j = y - 1; j <= y + 1; j++) {
			if (gray[i][j] > 10) return true;
		}
	}
	return false;
}
bool judge_circle(int idx) {
	int count = p_count[idx];
	if(count <= CIRCLECOUNTTHRESHOLD || count> CIRCLECOUNTTHRESHOLD*2) return false; //如果点数太小，没有参考性质
	long long sum_x = 0, sum_y = 0;
	long long c_x=0, c_y=0;  //中心点坐标
	int i;
	for (i = 0; i < count; i++) sum_x += idx_point_x[idx][i];  //得到x的坐标和
	for (i = 0; i < count; i++) sum_y += idx_point_y[idx][i];  //得到Y坐标和
	c_x =sum_x/ count;  //直接利用sum_y变量来作为中心点l
	c_y =sum_y/ count;
	long long avg_len = 0;
	for (int i = 0; i < count; i++) {
		avg_len += (idx_point_x[idx][i] - c_x) * (idx_point_x[idx][i] - c_x) + (idx_point_y[idx][i] - c_y) * (idx_point_y[idx][i] - c_y);
	}
	avg_len /= count;
	//long long mostdiff = 0;
	for (i = 0; i < count; i++) {
		/*if (abs((idx_point_x[idx][i] - c_x) * (idx_point_x[idx][i] - c_x) + (idx_point_y[idx][i] - c_y) * (idx_point_y[idx][i] - c_y) - avg_len) > mostdiff) mostdiff =
			abs((idx_point_x[idx][i] - c_x) * (idx_point_x[idx][i] - c_x) + (idx_point_y[idx][i] - c_y) * (idx_point_y[idx][i] - c_y) - avg_len);*/
		if (abs((idx_point_x[idx][i] - c_x) * (idx_point_x[idx][i] - c_x) + (idx_point_y[idx][i] - c_y) * (idx_point_y[idx][i] - c_y) - avg_len) > 200)  return false;
	}
	//printf("the diff is%d\n", mostdiff);
	if (circle_count > 3) {
		printf("circle over 4\n");
		circle_count++;
		return true;
	}
	circle_center[circle_count][0] = c_x, circle_center[circle_count][1] = c_y;
	printf("the circle is (%d ,%d)\n", c_x, c_y);
	return true;
	
}




//获得二维码的角度和偏移
void getAngleAndOffset() {
	int i,j ;
	for (i = 5; i +5< height; i++) {
		for (j = 5; j + 5 < width; j++) {
			if (gray[i][j]<10&&judge_edge(i,j)) {
				int t = find_pre(i, j); //看是否属于之前的某个点集
				if (t) finded[i][j] = t;  //标记当前点
				else { //另起门户
					finded[i][j] = ++p_idex;	//存入新的编号
					t = finded[i][j];
				}
				idx_point_x[t][p_count[t]] = pjx(j); //存入我们点信息
				idx_point_y[t][p_count[t]] = piy(i);
				p_count[t]++;  //点集增加
				color(i, j);
			 }
		}
	}
	int* tmp_count = (int *)malloc(sizeof(p_count));
	for (i = 0; i < p_idex; i++) tmp_count[i] = p_count[i];
	sort(tmp_count, p_idex);  //从大到小排序

	printf("the find points is %d\n", p_idex);
	int total = 0;
	for (i = 0; i < p_idex; i++) {
		total += p_count[i];
		printf("the index %d have %d points\n", i, tmp_count[i]);
	}
	printf("total:%d \n", total);

	//取前100个点集
	
	for (i = 0; i < p_idex; i++) {
		if (judge_circle(i)) {
			circle_count++;// 如果点集是圆，则增加	
		}
	}
	if (circle_count != 4) {
		printf("detect circle error,the count is%d\n",circle_count);
	}
	//


	//free(tmp_count);
}

void color(int i, int j) {
	output_r[i][j] = 255;
	output_g[i][j] = 0;
	output_b[i][j] = 0;
}

